package com.gupao.edu.homework.niocommunicate;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;

/**
 * @description：
 * @author：huabin.lin@xhmh.com
 * @version：2018年11月06日 20:12
 */
public class NioServerChannel {

    /***
     * 服务端默认端口
     */
    private static int DEFAULT_PORT = 8888;

    /***
     * 服务器地址
     */
    private InetSocketAddress localAddress;

    /***
     * 创建编码解码器
     */
    private Charset utf8;

    /***
     * 服务端默认端口启动
     */
    public void start() {
        start(DEFAULT_PORT);
    }

    /***
     * 服务端默认端口启动
     */
    public void start(int port) {
        if (null != localAddress) {
            return;
        }
        this.utf8 = Charset.forName("UTF-8");
        this.localAddress = new InetSocketAddress(port);

        // TCP通道
        ServerSocketChannel channel;
        // 选择器
        Selector selector = null;

        Random rnd = new Random();

        try {
            // 创建选择器
            selector = Selector.open();

            // 创建TCP通道
            channel = ServerSocketChannel.open();
            // 设置通道为非阻塞
            channel.configureBlocking(false);

            // 设置监听服务器的端口，设置最大连接缓冲数为100
            channel.bind(localAddress, 120);

            // 服务器通道只能对TCP链接事件感兴趣
            channel.register(selector, SelectionKey.OP_ACCEPT);
        } catch (IOException e1) {
            System.out.println("server start failed");
            return;
        }

        System.out.println("server start with address : " + localAddress);

        //<editor-fold desc="服务器线程被中断后会退出">
        try {
            while (!Thread.currentThread().isInterrupted()) {
                // 阻塞等待
                int n = selector.select();
                if (n == 0) {
                    continue;
                }

                // Set中的每个key代表一个通道
                Set<SelectionKey> keySet = selector.selectedKeys();
                Iterator<SelectionKey> it = keySet.iterator();
                SelectionKey key;

                //<editor-fold desc="遍历每个已就绪的通道，处理这个通道已就绪的事件">
                while (it.hasNext()) {
                    key = it.next();
                    // 防止下次select方法返回已处理过的通道
                    it.remove();

                    //<editor-fold desc="若发现异常，说明客户端连接出现问题,但服务器要保持正常">
                    try {
                        //<editor-fold desc="TCP通道只能对链接事件感兴趣">
                        if (key.isAcceptable()) {
                            // accept方法会返回一个普通通道，每个通道在内核中都对应一个socket缓冲区
                            SocketChannel sc = channel.accept();
                            // 设置通道为非阻塞
                            sc.configureBlocking(false);

                            // 向选择器注册这个通道和普通通道感兴趣的事件，同时提供这个新通道相关的缓冲区
                            int interestSet = SelectionKey.OP_READ;
                            sc.register(selector, interestSet, new NioBuffers(256, 256));

                            System.out.println("accept from " + sc.getRemoteAddress());
                        }
                        //</editor-fold>

                        //<editor-fold desc="（普通）通道感兴趣读事件且有数据可读">
                        if (key.isReadable()) {
                            // 通过SelectionKey获取通道对应的缓冲区
                            NioBuffers buffers = (NioBuffers) key.attachment();
                            ByteBuffer readBuffer = buffers.getReadBuffer();
                            ByteBuffer writeBuffer = buffers.getWriteBuffer();

                            // 通过SelectionKey获取对应的通道
                            SocketChannel sc = (SocketChannel) key.channel();

                            // 从底层socket读缓冲区中读入数据
                            sc.read(readBuffer);
                            readBuffer.flip();

                            // 解码显示，客户端发送来的信息
                            CharBuffer charBuffer = this.utf8.decode(readBuffer);
                            String[] content = new StringBuffer(charBuffer).toString().split("#@#");

                            String name = content[0];
                            System.out.println(String.format("欢迎 %1$s 进入聊天室！", name));

                            String data = "";
                            if (content.length > 1) {
                                data = content[1];
                                System.out.println(String.format("服务端接收到 %1$s 发送的信息：%2$s", name, data));
                            }

                            readBuffer.rewind();

                            // 准备好向客户端发送的信息
                            // 先写入"echo:"，再写入收到的信息
                            writeBuffer.put("echo from service:".getBytes(this.utf8));
                            writeBuffer.put(data.getBytes(this.utf8));

                            readBuffer.clear();

                            // 设置通道写事件
                            key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
                        }
                        //</editor-fold>

                        //<editor-fold desc="通道感兴趣写事件且底层缓冲区有空闲">
                        if (key.isWritable()) {
                            NioBuffers buffers = (NioBuffers) key.attachment();

                            ByteBuffer writeBuffer = buffers.getWriteBuffer();
                            writeBuffer.flip();

                            SocketChannel sc = (SocketChannel) key.channel();

                            int len = 0;
                            while (writeBuffer.hasRemaining()) {
                                try {
                                    len = sc.write(writeBuffer);
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                                // 说明底层的socket写缓冲已满
                                if (len == 0) {
                                    break;
                                }
                            }

                            writeBuffer.compact();

                            // 说明数据全部写入到底层的socket写缓冲区
                            if (len != 0) {
                                // 取消通道的写事件
                                key.interestOps(key.interestOps() & (~SelectionKey.OP_WRITE));
                            }
                        }
                        //</editor-fold>
                    } catch (IOException e) {
                        System.out.println("service encounter client error");
                        // 若客户端连接出现异常，从Seletcor中移除这个key
                        key.cancel();
                        key.channel().close();
                    }
                    //</editor-fold>
                }
                //</editor-fold>

                Thread.sleep(rnd.nextInt(500));
            }
        } catch (InterruptedException e) {
            System.out.println("serverThread is interrupted");
        } catch (IOException e1) {
            System.out.println("serverThread selecotr error");
        } finally {
            try {
                selector.close();
            } catch (IOException e) {
                System.out.println("selector close failed");
            } finally {
                System.out.println("server close");
            }
        }
        //</editor-fold>
    }

}
